﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: DynamicConstructorPersistentFactory.cs
//
//  Description: Creates new persistent object instances by the use of
//               code injection.
// 
//--------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;

namespace Chili.Opf3
{
	/// <summary>
	/// This class creates new persistent object instances by the use of
	/// code injection.
	/// </summary>
	public sealed class DynamicConstructorPersistentFactory : IPersistentFactory
	{
		// this delegate is used to encapsulate the injected constructor.
		private delegate object DynamicCtorDelegate();
		private static readonly object LOCK = new object();

		private Dictionary<Type, DynamicCtorDelegate> _constructorCache = new Dictionary<Type, DynamicCtorDelegate>();
		private DynamicCtorDelegate _lastDelegate;
		private Type _lastType;

		/// <summary>
		/// Creates an instance of a persistent object type. If the factory does not support 
		/// creation of the given type, it should return null.
		/// </summary>
		/// <param name="type">The type of the persistent to create an instance for.</param>
		/// <param name="parameters">The constructor parameters for the persistent object instance.</param>
		/// <returns>An instance if the factory can create one. Null if the factory can't create an instance for the given type.</returns>
		public object CreateInstance(Type type, object[] parameters)
		{
			// try to get the constructor from the cache.
			DynamicCtorDelegate constructor = null;

			// lock to make sure that we are alone in the following region.
			lock (LOCK)
			{
				// check if the last type matches the current one. this avoids
				// querying the dictionary.
				if (_lastType == type)
					constructor = _lastDelegate;
				else
				{
					// check if the constructor is already defined in the cache.
					if (!_constructorCache.TryGetValue(type, out constructor))
					{
						// create the constructor delegate.
						ConstructorInfo info = type.GetConstructor(
							BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
							null, Type.EmptyTypes, null);

						constructor = CreateConstructor(type, info);
						_constructorCache.Add(type, constructor);
					}
					
					// update the last constructor and type.
					_lastDelegate = constructor;
					_lastType = type;
				}
			}

			// return the persistent object instance.
			return constructor.Invoke();
		}

		/// <summary>
		/// Creates the injected constructor factory.
		/// </summary>
		/// <param name="type">The type for which the constructor factory is created.</param>
		/// <param name="constructor">The constructor itself.</param>
		private static DynamicCtorDelegate CreateConstructor(Type type, ConstructorInfo constructor)
		{
			if (constructor == null)
				throw new ArgumentNullException("constructor");
			if (constructor.GetParameters().Length > 0)
				throw new NotSupportedException("Only a constructor without arguments is supported.");

			// create the dynamic method.
			DynamicMethod dm = new DynamicMethod("ctor_" + type.Name, constructor.DeclaringType, Type.EmptyTypes, true);

			// inject the MSIL code.
			ILGenerator il = dm.GetILGenerator();
			il.Emit(OpCodes.Nop);
			il.Emit(OpCodes.Newobj, constructor);
			il.Emit(OpCodes.Ret);

			// return the delegate.
			return (DynamicCtorDelegate)dm.CreateDelegate(typeof(DynamicCtorDelegate));
		}
	}
}
